﻿/**
* CRL
*/
using CRL.Core;
using CRL;
using CRL.LambdaQuery;
using CRL.LambdaQuery.CRLExpression;
using Nest;
using Newtonsoft.Json;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace CRL.Elasticsearch
{
    public sealed partial class ESLambdaQuery<T> : LambdaQuery<T>
    {
        DbContextInner __dbContext;
        /// <summary>
        /// lambda查询
        /// </summary>
        /// <param name="_dbContext"></param>
        public ESLambdaQuery(DbContextInner _dbContext)
            : base(_dbContext, false)
        {
            __dbContext = _dbContext;
        }
        internal List<Dictionary<string, long>> _AggregationCount;
        //internal BoolQuery __filter = new BoolQuery();
        internal List<QueryContainer> queryContainers = new List<QueryContainer>();
        public void AddQueryBase(QueryBase query)
        {
            queryContainers.Add(query);
        }
        public override ILambdaQuery<T> Where(Expression<Func<T, bool>> expression)
        {
            if (expression == null)
                return this;
            var crlExpression = FormatExpression(expression.Body);
            var filterData = RouteCRLExpression(crlExpression);
            queryContainers.AddRange(filterData.Filters);
            return this;
        }

        #region 生成filter
        internal class FilterData
        {
            public List<QueryContainer> Filters { get; set; }
            public object Data { get; set; }
            public CRLExpressionType Type { get; set; }
        }
        List<QueryContainer> getFilter(FilterData left, FilterData right, ExpressionType expressionType)
        {
            var containers = new List<QueryContainer>();
            QueryContainer filter;
            if (left.Type == CRLExpressionType.Binary || left.Type == CRLExpressionType.Tree)//表示二元运算
            {
                #region 按条件组合
                switch (expressionType)
                {
                    case ExpressionType.AndAlso:
                        //containers.AddRange(left.Filters);
                        //containers.AddRange(right.Filters);
                        var c1 = new List<QueryContainer>();
                        c1.AddRange(left.Filters);
                        c1.AddRange(right.Filters);
                        var bq1 = new BoolQuery() { Filter = c1 };
                        containers.Add(bq1);
                        break;
                    case ExpressionType.OrElse:
                        var c2 = new List<QueryContainer>();
                        var qLeft = new BoolQuery() { Filter = left.Filters };
                        var qRight = new BoolQuery() { Filter = right.Filters };
                        c2.Add(left.Filters.Count == 1 ? left.Filters.First() : qLeft);
                        c2.Add(right.Filters.Count == 1 ? right.Filters.First() : qRight);
                        var bq2 = new BoolQuery
                        {
                            Should = c2
                        };
                        containers.Add(bq2);
                        break;
                    default:
                        throw new InvalidCastException("不支持的运算符");
                }
                #endregion
            }
            else if (left.Type == CRLExpressionType.MethodCallArgs)
            {
                var methodInfo = left.Data as MethodCallObj;
                #region 按方法
                var field = methodInfo.MemberName;
                var args = methodInfo.Args;
                var firstArgs = args.FirstOrDefault();
                switch (methodInfo.MethodName)
                {
                    case "Contains":
                        if (firstArgs is IEnumerable && firstArgs.GetType() != typeof(string))//如果是集合,按in
                        {
                            var list1 = convertIEnumerable(firstArgs);
                            filter = EsQueryCreater.CreateTermsQuery(field, list1);
                            break;
                        }
                        filter = EsQueryCreater.CreateWildcardQuery(field, $"*{firstArgs}*");
                        break;
                    case "StartsWith":
                        filter = EsQueryCreater.CreateWildcardQuery(field, $"{firstArgs}*");
                        break;
                    case "Like":
                        filter = EsQueryCreater.CreateWildcardQuery(field, $"*{firstArgs}*");
                        break;
                    case "LikeLeft":
                        filter = EsQueryCreater.CreateWildcardQuery(field, $"{firstArgs}*");
                        break;
                    case "LikeRight":
                        filter = EsQueryCreater.CreateWildcardQuery(field, $"{firstArgs}*");
                        break;
                    case "Between":
                        filter = EsQueryCreater.CreateRangeQuery(args[0].GetType(), false, field, args[0], args[1]);
                        break;
                    case "DateDiff":
                        throw new NotSupportedException(methodInfo.MethodName);
                    case "IsNullOrEmpty":
                        filter = EsQueryCreater.CreateTermQuery(field, "");
                        break;
                    case "In":
                        var list2 = convertIEnumerable(firstArgs);
                        filter = EsQueryCreater.CreateTermsQuery(field, list2);
                        break;
                    case "NotIn":
                        var list3 = convertIEnumerable(firstArgs);
                        var bq = new BoolQuery();
                        bq.MustNot = new List<QueryContainer>() { EsQueryCreater.CreateTermsQuery(field, list3) };
                        filter = bq;
                        break;
                    case "Substring":
                        throw new NotSupportedException(methodInfo.MethodName);
                    case "Equals":
                        filter = EsQueryCreater.CreateEqualQuery(field, firstArgs);
                        break;
                    #region es query ext
                    case "WithIdsQuery":
                        var list4 = convertIEnumerable(firstArgs).Select(b => (Id)b);
                        filter = EsQueryCreater.CreateIdsQuery(list4);
                        break;
                    case "WithWildcardQuery":
                        filter = EsQueryCreater.CreateWildcardQuery(field, firstArgs);
                        break;
                    case "WithTermQuery":
                        filter = EsQueryCreater.CreateTermQuery(field, firstArgs);
                        break;
                    case "WithTermsQuery":
                        var list5 = convertIEnumerable(firstArgs);
                        filter = EsQueryCreater.CreateTermsQuery(field, list5);
                        break;
                    case "WithMatchQuery":
                        filter = EsQueryCreater.CreateMatchQuery(field, firstArgs.ToString(), args.Count > 1 ? (Nest.Operator)args[1] : Nest.Operator.And);
                        break;
                    case "WithFuzzyQuery":
                        filter = EsQueryCreater.CreateFuzzyQuery(field, firstArgs.ToString());
                        break;
                    case "WithMatchPhraseQuery":
                        filter = EsQueryCreater.CreateMatchPhraseQuery(field, firstArgs.ToString(), args.Count > 1 ? (int)args[1] : 0);
                        break;
                    case "WithMultiMatchQuery":
                        var fields = convertIEnumerable(firstArgs).Select(b => b.ToString()).ToArray();
                        filter = EsQueryCreater.CreateMultiMatchQuery(fields, args[1].ToString(), (Nest.Operator)args[2], (TextQueryType)args[3], args.Count > 4 ? (int)args[4] : 0);
                        break;
                    case "WithQueryBase":
                        filter = (QueryBase)firstArgs;
                        break;
                    #endregion
                    default:
                        throw new NotSupportedException(methodInfo.MethodName);//不支持
                }
                if (expressionType == ExpressionType.Not)//创建反向操作
                {
                    var bq = new BoolQuery();
                    bq.MustNot = new List<QueryContainer>() { filter };
                    filter = bq;
                }
                #endregion
                containers.Add(filter);
            }
            else//按值
            {
                #region 按值
                string name;
                object value;
                if (left.Type == CRLExpressionType.Name)
                {
                    name = left.Data.ToString();
                    value = right.Data;
                }
                else
                {
                    name = right.Data.ToString();
                    value = left.Data;
                }
                if (value == null)
                {
                    throw new ArgumentNullException("value");
                }
                var dataType = value.GetType();
                switch (expressionType)
                {
                    case ExpressionType.Equal:
                        filter = EsQueryCreater.CreateEqualQuery(name, value);
                        break;
                    case ExpressionType.GreaterThan:
                        filter = EsQueryCreater.CreateRangeQuery(dataType, false, name, null, value);
                        break;
                    case ExpressionType.GreaterThanOrEqual:
                        filter = EsQueryCreater.CreateRangeQuery(dataType, true, name, null, value);
                        break;
                    case ExpressionType.LessThan:
                        filter = EsQueryCreater.CreateRangeQuery(dataType, false, name, value, null);
                        break;
                    case ExpressionType.LessThanOrEqual:
                        filter = EsQueryCreater.CreateRangeQuery(dataType, true, name, value, null);
                        break;
                    case ExpressionType.NotEqual:
                        var bq = new BoolQuery();
                        bq.MustNot = new List<QueryContainer>() { EsQueryCreater.CreateEqualQuery(name, value) };
                        filter = bq;
                        break;
                    default:
                        throw new InvalidCastException("不支持的运算符");
                }
                containers.Add(filter);
                #endregion
            }
            return containers;
        }
        List<object> convertIEnumerable(object args)
        {
            var list = args as IEnumerable;
            var list2 = new List<object>();
            foreach (var s in list)
            {
                list2.Add(s);
            }
            return list2;
        }
        FilterData BinaryCRLExpression(CRLExpression left, CRLExpression right, ExpressionType expressionType)
        {
            var parLeft = RouteCRLExpression(left);
            var parRight = RouteCRLExpression(right);
            return new FilterData() { Filters = getFilter(parLeft, parRight, expressionType), Type = CRLExpressionType.Binary };
        }
        internal FilterData RouteCRLExpression(CRLExpression exp)
        {
            if (exp.Type == CRLExpressionType.Binary || exp.Type == CRLExpressionType.Tree)//表示二元运算
            {
                return BinaryCRLExpression(exp.Left, exp.Right, exp.ExpType);
            }
            else if (exp.Type == CRLExpressionType.MethodCall)
            {
                var methodInfo = exp.Data as MethodCallObj;
                var left = new CRLExpression() { ExpType = methodInfo.ExpressionType, Type = CRLExpressionType.MethodCallArgs, Data = methodInfo };
                var right = new CRLExpression() { ExpType = methodInfo.ExpressionType, Type = CRLExpressionType.MethodCallArgs, Data = methodInfo.Args };
                return BinaryCRLExpression(left, right, methodInfo.ExpressionType);
            }
            //按值
            var filter = new FilterData() { Data = exp.Data, Type = exp.Type };
            if (exp.OriginData != null)
            {
                filter.Data = exp.OriginData;
            }
            return filter;
        }

        #endregion
        internal List<ISort> _ESSort = new List<ISort>();
        public override ILambdaQuery<T> OrderBy<TResult>(Expression<Func<T, TResult>> expression, bool desc = true)
        {
            //var sortBuild = Builders<T>.Sort;
            var parameters = expression.Parameters.Select(b => b.Type).ToArray();
            var field = GetSelectField(false, expression.Body, false, parameters).mapping.First();
            _ESSort.Add(new FieldSort()
            {
                Field = field.ResultName,
                Order = desc ? SortOrder.Descending : SortOrder.Ascending,
            });
            return this;
        }
        public override ILambdaQuery<T> OrderBy(string orderBy)
        {
            var arry = orderBy.Split(' ');
            if (arry.Length != 2)
            {
                throw new Exception("参数格式不正确 field order");
            }
            _ESSort.Add(new FieldSort()
            {
                Field = arry[0],
                Order = arry[1] == "desc" ? SortOrder.Descending : SortOrder.Ascending,
            });
            return this;
        }

        public override ILambdaQuery<T> OrderByPrimaryKey(bool desc)
        {
            //es 不能按字符串排序
            return this;
        }

        public override ILambdaQuery<T> Or(Expression<Func<T, bool>> expression)
        {
            var crlExpression = FormatExpression(expression.Body);
            var filterData = RouteCRLExpression(crlExpression);
            var c2 = new List<QueryContainer>();
            c2.AddRange(queryContainers);
            c2.AddRange(filterData.Filters);
            var bq = new BoolQuery() { Should = c2 };
            queryContainers = new List<QueryContainer>() { bq };
            return this;
        }
        #region NotSupported

        public override string GetQueryFieldString()
        {
            return "";
        }

        public override void GetQueryConditions(StringBuilder sb, bool withTableName = true)
        {
            //return "";
        }

        public override string GetOrderBy()
        {
            return ""; throw new NotImplementedException();
        }

        public override string GetQuery(bool v)
        {
            var db = DBExtendFactory.CreateDBExtend(__dbContext) as ESEx.ESExtend;
            var json = db.getQueryJson(db.getSearchRequest(this));
            json = ConvertJsonString(json);
            return json;
        }
        static string ConvertJsonString(string str)
        {
            //格式化json字符串
            JsonSerializer serializer = new JsonSerializer();
            TextReader tr = new StringReader(str);
            JsonTextReader jtr = new JsonTextReader(tr);
            object obj = serializer.Deserialize(jtr);
            if (obj != null)
            {
                StringWriter textWriter = new StringWriter();
                JsonTextWriter jsonWriter = new JsonTextWriter(textWriter)
                {
                    Formatting = Formatting.Indented,
                    Indentation = 4,
                    IndentChar = ' '
                };
                serializer.Serialize(jsonWriter, obj);
                return textWriter.ToString();
            }
            else
            {
                return str;
            }
        }
        #endregion

        public IReadOnlyCollection<AnalyzeToken> Analyze(string txt, string analyser)
        {
            var db = DBExtendFactory.CreateDBExtend(__dbContext) as ESEx.ESExtend;
            return db.Analyze(txt, analyser);
        }
    }
}
